/* ******************************************************************************
 * Copyright Notice:
 * Copyright 2023-2024, Huawei Technologies Co., Ltd. ALL Rights Reserved.
 *
 * Warning: This computer software sourcecode is protected by copyright law
 * and international treaties. Unauthorized reproduction or distribution
 * of this sourcecode, or any portion of it, may result in severe civil and
 * criminal penalties, and will be prosecuted to the maximum extent
 * possible under the law.
 * *******************************************************************************
 */

package com.android.server.pm;

import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.FileObserver;
import android.os.Process;
import android.util.Log;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

public class AppIdSettingMapHelper {

    private static final String TAG = "AppIdSettingMapHelper";
    private static final int FIRST_ASSIGN_APP_UID = 16000;
    private final Object mLock = new Object();
    private final String APP_CONFIG_PATH = "/data/local/tmp/share_apps.xml";
    private Map<String, Integer> mAssignAppInfo = new HashMap<>();
    private Map<Integer, Object> mAssignUids = new HashMap<>();
    private boolean mXmlHasRead = false;
    private FileObserver mFileObserver;

    private void startFileObservation() {
        File xmlFile = new File(APP_CONFIG_PATH);
        mFileObserver = new FileObserver(xmlFile.getPath(), FileObserver.ALL_EVENTS) {
            @Override
            public void onEvent(int event, String path) {
                switch (event) {
                    case FileObserver.MODIFY:
                        parseAssignAppXml();
                        Log.i(TAG, "share_apps.xml has been modify.");
                        break;
                    case FileObserver.DELETE:
                    case FileObserver.DELETE_SELF:
                        synchronized (mLock) {
                            mXmlHasRead = false;
                        }
                        Log.i(TAG, "share_apps.xml has been deleted.");
                        break;
                    default:
                        break;
                }
            }
        };
        Log.i(TAG, "Start watching share_apps.xml.");
        mFileObserver.startWatching();
    }

    protected static int getFirstAvailableUid() {
        return Process.FIRST_APPLICATION_UID;
    }

    public int getLastApplicationUid() {
        return FIRST_ASSIGN_APP_UID - 1;
    }

    public AppIdSettingMapHelper() {
        checkXmlFile();
    }

    public boolean isLastApplicationUid(int mFirstAvailableUid, ArrayList<SettingBase> mUserIds, int sum) {
        // If mFirstAvailableUid increase to Process.LAST_APPLICATION_UID,
        // just looking for available uid from index 0.
        return (mFirstAvailableUid > Process.LAST_APPLICATION_UID)
                || (mFirstAvailableUid == Process.LAST_APPLICATION_UID && mUserIds.get(sum - 1) != null);
    }

    private void addAssignAppInfo(String packageName, int uid) {
        if (isAssignAppUid(uid)) {
            mAssignAppInfo.put(packageName, uid);
        }
    }

    private void checkXmlFile() {
        File file = new File(APP_CONFIG_PATH);
        if (!file.exists()) {
            Log.i(TAG, "app config file not find.");
            return;
        }
        synchronized (mLock) {
            if (mXmlHasRead) {
                return;
            }
        }
        parseAssignAppXml();
        // 监听xml是否变化
        startFileObservation();
    }

    private void parseAssignAppXml() {
        synchronized (mLock) {
            FileReader fileReader = null;
            try {
                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                factory.setNamespaceAware(true);
                XmlPullParser parser = factory.newPullParser();
                File file = new File(APP_CONFIG_PATH);
                fileReader = new FileReader(file);
                parser.setInput(fileReader);
                int eventType = parser.getEventType();
                while (eventType != XmlPullParser.END_DOCUMENT) {
                    if (eventType != XmlPullParser.START_TAG) {
                        eventType = parser.next();
                        continue;
                    }
                    String tag = parser.getName();
                    if (tag.equals("package")) {
                        String packageName = parser.getAttributeValue(null, "name");
                        int uid = Integer.parseInt(parser.getAttributeValue(null, "userId"));   
                        addAssignAppInfo(packageName, uid);
                        Log.i(TAG, "package = " + packageName + " uid = " + uid);
                    }
                    eventType = parser.next();
                }
            } catch (XmlPullParserException e) {
                Log.e(TAG, "xml parse exception," + e);
            } catch (IOException e) {
                Log.e(TAG, "xml parse IOException," + e);
            } catch (Exception e) {
                Log.e(TAG, "exception," + e);
            } finally {
                if (fileReader != null) {
                    try {
                        fileReader.close();
                    } catch (IOException e) {
                        Log.e(TAG, "filereader close exception," + e);
                    }
                }
                mXmlHasRead = true;
            }
        }
    }

    public boolean isAssignAppUid(int uid) {
        if (uid >= FIRST_ASSIGN_APP_UID && uid <= Process.LAST_APPLICATION_UID) {
            return true;
        } else {
            return false;
        }
    }

    public int newAssignAppUid(Object obj) {
        checkXmlFile();
        if (mAssignAppInfo.isEmpty()) {
            return -1;
        }
        if (obj == null || !(obj instanceof PackageSetting)) {
            return -1;
        }
        PackageSetting p = (PackageSetting) obj;
        String pkgName = p.getPackageName();
        int uid = mAssignAppInfo.getOrDefault(pkgName, -1);
        if (uid == -1) {
            return -1;
        }
        if (mAssignUids.containsKey(uid)) {
            return -1;
        }
        mAssignUids.put(uid, obj);
        Log.i(TAG, "Create uid " + uid + "success.");
        return uid;
    }

    public boolean addAssignAppUid(int uid, Object obj) {
        if (mAssignUids.getOrDefault(uid, null) != null) {
            return false;
        }
        mAssignUids.put(uid, obj);
        return true;
    }

    public void removeAssignAppUid(int uid) {
        mAssignUids.remove(uid);
        Log.i(TAG, "Remove uid " + uid + "success.");
    }

    public Object getAssignUidObject(int uid) {
        return mAssignUids.getOrDefault(uid, null);
    }

    public void replaceAssignAppUid(int uid, Object obj) {
        mAssignUids.put(uid, obj);
    }
}
 